package net.sansi.v3correctserver.util;

import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import net.sansi.v3correctserver.entity.IntPoint;
import net.sansi.v3correctserver.exception.ServiceException;

import java.io.Serializable;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;

/**
 * Created by Fang
 * create time : 2024/11/4
 * IDE : IntelliJ IDEA
 */

@Getter
public class Matrix<T> implements Serializable {

    private final T[][] data;
    private final int width;
    private final int height;
    private int itemSize = 0;
    @Setter
    @Accessors(chain = true)
    private FileLoader<T> loader;

    @SuppressWarnings("unchecked")
    public Matrix(int width, int height) {
        this.width = width;
        this.height = height;
        // 使用反射创建泛型数组
        this.data = (T[][]) new Object[width][height];
    }

    /**
     * 创建一个指定大小的矩阵，并填充默认值
     *
     * @param width        宽度
     * @param height       高度
     * @param defaultValue 默认值
     */
    public Matrix(int width, int height, T defaultValue) {
        this(width, height);
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                set(i, j, defaultValue);
            }
        }
    }

    /**
     * 使用提供的函数生成矩阵
     *
     * @param width    宽度
     * @param height   高度
     * @param supplier 生成元素的函数
     */
    public Matrix(int width, int height, MatrixSupplier<T> supplier) {
        this(width, height);
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                this.set(i, j, supplier.get(i, j));
            }
        }
    }

    /**
     * 使用提供的函数生成矩阵
     *
     * @param width    宽度
     * @param height   高度
     * @param supplier 生成元素的函数
     * @param <T>      类型
     * @return 矩阵
     */
    public static <T> Matrix<T> of(int width, int height, MatrixSupplier<T> supplier) {
        return new Matrix<>(width, height, supplier);
    }

    /**
     * 从文件中加载矩阵
     *
     * @param path 文件路径
     * @return Matrix对象
     */
    public static <T> Matrix<T> load(FileLoader<T> loader, Path path) {
        return loader.load(path).setLoader(loader);
    }

    /**
     * 保存矩阵到文件
     *
     * @param path 文件路径
     */
    public void save(Path path) {
        if (loader == null) {
            throw new ServiceException("保存matrix失败，未设置加载器!");
        }
        loader.save(this, path);
    }

    /**
     * 将矩阵转换为列表
     *
     * @return 列表
     */
    public List<T> toList() {
        List<T> res = new ArrayList<>();
        this.foreach((x, y, value) -> {
            if (value == null) return;
            res.add(value);
        });
        return res;
    }

    /**
     * 将矩阵转换为列表
     * 1. 从中间开始广度优先搜索排序
     *
     * @return 列表
     */
    public List<T> toListStartCenter() {
        int[] DX = new int[]{0, 1, 0, -1};
        int[] DY = new int[]{1, 0, -1, 0};
        int[] p = new int[]{width / 2, height / 2};
        boolean[][] vis = new boolean[width][height];
        Deque<int[]> deq = new LinkedList<>();
        deq.add(p);
        List<T> res = new ArrayList<>();
        while (!deq.isEmpty()) {
            int[] pos = deq.poll();
            int x = pos[0], y = pos[1];
            if (vis[x][y]) continue;
            vis[x][y] = true;
            if (get(x, y) != null) res.add(get(x, y));
            for (int i = 0; i < 4; i++) {
                int nx = x + DX[i], ny = y + DY[i];
                if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue;
                if (vis[nx][ny]) continue;
                deq.add(new int[]{nx, ny});
            }
        }
        return res;
    }


    /**
     * 创建一个指定大小的矩阵，并使用提供的函数生成元素
     *
     * @param <T>
     */
    public interface MatrixSupplier<T> {
        T get(int x, int y);
    }

    /**
     * 遍历矩阵，使用提供的函数处理每个元素
     *
     * @param <T>
     */
    public interface MatrixConsumer<T> {
        void accept(int x, int y, T value);
    }

    /**
     * 遍历矩阵，使用提供的函数处理每个元素
     *
     * @param consumer 处理函数
     */
    public void foreach(MatrixConsumer<T> consumer) {
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                consumer.accept(i, j, data[i][j]);
            }
        }
    }

    /**
     * 断言函数
     *
     * @param <T> 元素类型
     */
    public interface MatrixPredicate<T> {
        boolean test(int x, int y, T value);
    }

    /**
     * 过滤矩阵元素
     *
     * @param predicate 断言函数
     * @return 结果
     */
    public List<T> filter(MatrixPredicate<T> predicate) {
        List<T> res = new ArrayList<>();
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                if (predicate.test(i, j, data[i][j])) res.add(data[i][j]);
            }
        }
        return res;
    }

    /**
     * 过滤矩阵元素某行
     *
     * @param row       行号
     * @param predicate 断言
     * @return 结果
     */
    public List<T> filterRow(int row, MatrixPredicate<T> predicate) {
        List<T> res = new ArrayList<>();
        for (int i = 0; i < width; i++) {
            if (predicate.test(i, row, data[i][row])) res.add(data[i][row]);
        }
        return res;
    }

    /**
     * 过滤矩阵元素某列
     *
     * @param col       列好
     * @param predicate 断言
     * @return 结果
     */
    public List<T> filterCol(int col, MatrixPredicate<T> predicate) {
        List<T> res = new ArrayList<>();
        for (int i = 0; i < height; i++) {
            if (predicate.test(col, i, data[col][i])) res.add(data[col][i]);
        }
        return res;
    }

    /**
     * 裁剪
     *
     * @param lt 左上角
     * @param rb 右上角
     * @return 裁剪结果
     */
    public Matrix<T> clip(IntPoint lt, IntPoint rb) {
        int w = rb.getX() - lt.getX() + 1;
        int h = rb.getY() - lt.getY() + 1;
        Matrix<T> res = new Matrix<>(w, h);
        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h; j++) {
                res.set(i, j, data[i + lt.getX()][j + lt.getY()]);
            }
        }
        return res;
    }

    public interface ConvertItem<T, B> {
        B convert(int x, int y, T value);
    }

    /**
     * 将矩阵元素转换为另一个类型
     *
     * @param convertItem 转换函数
     * @param <B>         目标类型
     * @return 转换矩阵
     */
    public <B> Matrix<B> map(ConvertItem<T, B> convertItem) {
        return new Matrix<>(width, height, (x, y) -> convertItem.convert(x, y, data[x][y]));
    }

    /**
     * 设置矩阵元素
     *
     * @param x     x轴坐标
     * @param y     y轴坐标
     * @param value 元数值
     */
    public void set(int x, int y, T value) {
        T oldValue = data[x][y];
        if (oldValue == null && value != null) itemSize++;
        if (oldValue != null && value == null) itemSize--;
        data[x][y] = value;
    }

    /**
     * 设置矩阵元素
     *
     * @param point 坐标
     * @param value 元数值
     */
    public void set(IntPoint point, T value) {
        set(point.getX(), point.getY(), value);
    }

    /**
     * 移除矩阵元素
     *
     * @param x x坐标
     * @param y y坐标
     */
    public void remove(int x, int y) {
        T oldValue = data[x][y];
        if (oldValue != null) itemSize--;
        data[x][y] = null;
    }

    /**
     * 获取矩阵元素
     *
     * @param x x轴坐标
     * @param y y轴坐标
     * @return 元数值
     */
    public T get(int x, int y) {
        return data[x][y];
    }

    /**
     * 获取矩阵元素
     *
     * @param point 坐标
     * @return 元数值
     */
    public T get(IntPoint point) {
        return get(point.getX(), point.getY());
    }

    /**
     * 获取矩阵元素，如果不存在则创建
     *
     * @param x        x坐标
     * @param y        y 坐标
     * @param setValue 设置值
     * @return 结果
     */
    public T getOrSet(int x, int y, T setValue) {
        T value = get(x, y);
        if (value == null) {
            set(x, y, setValue);
            return setValue;
        }
        return value;
    }

    /**
     * 获取矩阵元素，如果不存在则创建
     *
     * @param point    坐标
     * @param setValue 设置值
     * @return 结果
     */
    public T getOrSet(IntPoint point, T setValue) {
        return getOrSet(point.getX(), point.getY(), setValue);
    }

    /**
     * 转换成样本
     *
     * @param xSpan x 隔点取样
     * @param ySpan y 隔点取样
     * @return 样本矩阵
     */
    public Matrix<T> toSample(int xSpan, int ySpan) {
        int w = width / xSpan, h = height / ySpan;
        Matrix<T> res = new Matrix<>(w, h);
        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h; j++) {
                res.set(i, j, get(i * xSpan, j * ySpan));
            }
        }
        return res;
    }

    /**
     * 转换成字符串矩阵
     *
     * @param <T> 元素类型
     */
    public interface ToStringItem<T> {
        String toString(T item);
    }

    /**
     * 转换成字符串矩阵
     *
     * @param toStringItem 转换函数
     * @return 字符串表格矩阵
     */
    public Matrix<String> toStringMatrix(ToStringItem<T> toStringItem) {
        return Matrix.of(width, height, (x, y) -> {
            T t = get(x, y);
            return toStringItem.toString(t);
        });
    }

    public Matrix<String> toStringMatrix() {
        return toStringMatrix(Object::toString);
    }

    /**
     * 将坐标添加到矩阵中
     *
     * @param xSpan  横向
     * @param ySpan  纵向
     * @param matrix 目标矩阵
     * @return 结果
     */
    public static Matrix<String> toMatrixWithCoord(int xSpan, int ySpan, Matrix<String> matrix) {
        int w = matrix.getWidth(), h = matrix.getHeight();
        Matrix<String> res = new Matrix<>(h + 1, w + 1);
        for (int i = 0; i < w; i++) res.set(i + 1, 0, String.valueOf(i * xSpan));
        for (int i = 0; i < h; i++) res.set(0, i + 1, String.valueOf(i * ySpan));
        for (int i = 1; i <= w; i++) {
            for (int j = 1; j <= h; j++) {
                res.set(i, j, matrix.get(i - 1, j - 1));
            }
        }
        return res;
    }

    public void prettyPrint(int xScale, int yScale) {
        int[] colWidths = new int[width + 1];
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                colWidths[i + 1] = Math.max(colWidths[i + 1], String.valueOf(get(i, j)).length());
                colWidths[i + 1] = Math.max(colWidths[i + 1], String.valueOf(i * xScale).length());
            }
        }
        for (int i = 0; i < height; i++) {
            colWidths[0] = Math.max(colWidths[0], String.valueOf(i * yScale).length());
        }
        printLine(genList(width + 1, -1, xScale), colWidths);
        for (int y = 0; y < height; y++) {
            String[] line = new String[width + 1];
            line[0] = String.valueOf(y * yScale);
            for (int x = 0; x < width; x++) {
                line[x + 1] = String.valueOf(get(x, y));
            }
            printLine(line, colWidths);
        }
    }

    private String[] genList(int count, int start, int xScale) {
        String[] res = new String[count];
        for (int i = 0; i < count; i++) {
            res[i] = String.valueOf((start + i) * xScale);
        }
        return res;
    }

    private void printLine(String[] line, int[] colWidths) {
        System.out.print(" ");
        for (int i = 0; i < line.length; i++) {
            System.out.printf("%" + colWidths[i] + "s ", line[i]);
        }
        System.out.println();
    }


    /**
     * 矩阵转文件
     */
    public interface FileLoader<T> {
        /**
         * 读取文件
         *
         * @param path 文件路径
         * @return 矩阵
         */
        Matrix<T> load(Path path);

        /**
         * 保存文件
         *
         * @param path 文件路径
         */
        void save(Matrix<T> matrix, Path path);
    }

}
